/**
 * TetisDB
 *
 * Classe para codificacao do objeto a ser gravado
 */

package br.com.tetisdb.io.snapShot.encoder;

import br.com.tetisdb.io.snapShot.*;
import br.com.tetisdb.lang.TetisObject;
import br.com.tetisdb.util.TetisUtil;
import java.lang.reflect.Field;

import br.com.tetisdb.lang.TetisHashMap;
import java.lang.reflect.Modifier;
import java.util.List;

public class Encoder extends TetisObject {
    //snapShot
    private TetisHashMap<String, Object> snapShot;
    
    /**
     * Construtor padrao
     *
     * @param snapShot SnapShot que esse encoder deve trabalhar.
     */
    public Encoder(TetisHashMap<String, Object> snapShot) {
        tetisLogger.debug("Criando Encoder.");
        this.snapShot = snapShot;
    }
    
    /*
     * recebe um object e adiciona ele, e seus filhos, no snapShot
     */
    private TetisHashMap<String, Object> encode(Object object, boolean recursivo) {
        try {
            tetisLogger.debug("Codificando o objeto: "+object);
            TetisHashMap<String, Object> hash = new TetisHashMap<String, Object>();
            Class clazz = object.getClass();
            String atributos = "";
            String tipos = "";
            List<Field> fields = TetisUtil.getFields(clazz);
            
            for (Field field : fields) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                
                String valor = ""; //valor que sera colocado no hash
                
                if(!Modifier.isFinal(field.getModifiers())) { //nao pode alterar tipo final, portanto nao le.
                    if ((!field.getType().isPrimitive()) && (!field.getType().getName().startsWith("java.lang"))) {
                        Object o = field.get(object);
                        
                        if(o == null) {
                            valor = "null";
                        } else if (!o.getClass().isInstance(object)) { // para resolver bug de recursividade eterna
                            snapShot.put(key(o), encode(o, true));
                            valor = atualKey(o);
                        }
                        
                    } else {
                        Object o = field.get(object);
                        
                        if(o == null) {
                            if(field.getType().getName().equalsIgnoreCase("java.lang.String")) {
                                valor = "null";
                            } else if (field.getType().getName().equalsIgnoreCase("java.lang.Boolean")) {
                                valor = "false";
                            } else {
                                valor = "0";
                            }
                        } else {
                            valor = o.toString();
                        }
                    }
                    
                    String nome = field.getName();
                    atributos += nome + SnapShotManager.PIPE;
                    tipos += field.getType().getName() + SnapShotManager.PIPE;
                    
                    hash.put(nome, valor);
                } else {
                    tetisLogger.debug("Atributo FINAL encontrado, ignorado.");
                }
            }
            
            hash.put(SnapShotManager.ATRIBUTOS, atributos);
            hash.put(SnapShotManager.TIPOSATRIBUTOS, tipos);
            hash.put(SnapShotManager.NOMECLASSE, clazz.getName());
            
            if (!recursivo) {
                snapShot.put(key(object), hash);
            }
          
            return hash;
            
        } catch (Exception e) {
            tetisLogger.error("Erro ao codificar objeto. Erro: "+e.getMessage());
            return null;
        }
    }
    
    /**
     * Metodo para codificacao do objeto.
     *
     * @param object Objeto a ser gravado.
     * @return TetisHashMap Objeto codificado.
     */
    public TetisHashMap<String, Object> encode(Object object) {
        return encode(object, false);
    }
    
    /*
     * restaga a chave atual para o objeto passado
     */
    private String atualKey(Object o) throws Exception {
        TetisHashMap<String, Object> hash = (TetisHashMap<String, Object>) snapShot.get(SnapShotManager.INDICE);
        
        if (hash == null) {
            return key(o);
        }
        
        Integer i = (Integer) hash.get(o.getClass().getName());
        
        return o.getClass().getName() + "@" + String.valueOf(i.intValue());
    }
    
    /*
     * cria um chave nova para o objeto passado
     */
    private String key(Object o) throws Exception {
        TetisHashMap<String, Object> hash = (TetisHashMap<String, Object>) snapShot.get(SnapShotManager.INDICE);
        
        if (hash == null) {
            hash = new TetisHashMap<String, Object>();
        }
        
        Integer i = (Integer) hash.get(o.getClass().getName());
        int novoValor = (i !=null ? i.intValue() + 1 : 1);
        
        hash.put(o.getClass().getName(), new Integer(novoValor));
        snapShot.put(SnapShotManager.INDICE, hash);
        
        return o.getClass().getName() + "@" + String.valueOf(novoValor);
    }
}
